#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netdb.h>
#include <unistd.h>
#include <getopt.h>
#include "base64_utils.h"

#define MAX_SIZE 4095

char buf[MAX_SIZE+1];

// s_fd: socket file descriptor
void print_response(int s_fd, char *buf){
    int r_size;
    if ((r_size = recv(s_fd, buf, MAX_SIZE, 0)) == -1)
    {
        perror("recv");
        exit(EXIT_FAILURE);
    }
    buf[r_size] = '\0'; 
    printf("%s", buf);
}

// receiver: mail address of the recipient
// subject: mail subject
// msg: content of mail body or path to the file containing mail body
// att_path: path to the attachment
void send_mail(const char* receiver, const char* subject, const char* msg, const char* att_path)
{
    const char* end_msg = "\r\n.\r\n";
    const char* host_name = "smtp.163.com"; // Specify the mail server domain name "smtp.163.com"
    const unsigned short port = 25; // SMTP server port
    const char* user = "mail_lab_test@163.com"; // Specify the user
    const char* pass = "OEUDQYINOTFJCYGJ"; // Specify the password
    const char* from = "comp2008@hitsz-lab.com"; // Specify the mail address of the sender
    char dest_ip[16] ; // Mail server IP address = "202.181.15.161"
    int s_fd; // socket file descriptor
    struct hostent *host;
    struct in_addr **addr_list;
    int i = 0;
    int r_size;

    // Get IP from domain name
    if ((host = gethostbyname(host_name)) == NULL)
    {
        herror("gethostbyname");
        exit(EXIT_FAILURE);
    }

    addr_list = (struct in_addr **) host->h_addr_list;
    while (addr_list[i] != NULL)
        ++i;
    strcpy(dest_ip, inet_ntoa(*addr_list[i-1]));

    // Create a socket, return the file descriptor to s_fd, and establish a TCP connection to the mail server
    s_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (s_fd == -1){
        perror("create a socket");
        exit(EXIT_FAILURE);
    }
    // 连接服务器
    struct sockaddr_in sad_in = {
        .sin_port = htons(port),
        .sin_family = AF_INET,
        .sin_addr.s_addr = inet_addr(dest_ip)//addr_list[i-1]->s_addr
    };
    if (connect(s_fd, (struct sockaddr *)&sad_in, sizeof(sad_in)) == -1){
        perror("connect");
        exit(EXIT_FAILURE);
    }
    
    // Print welcome message
    print_response(s_fd, buf);

    // Send EHLO command and print server response
    const char* EHLO = "EHLO 163.com\r\n"; 
    send(s_fd, EHLO, strlen(EHLO), 0);

    // Print server response to EHLO command
    print_response(s_fd, buf);

    // Authentication. Server response should be printed out.
    const char* AUTH = "AUTH login\r\n"; 
    send(s_fd, AUTH, strlen(AUTH), 0);

    // Print server response to AUTH command
    print_response(s_fd, buf);

    // Send user and pass
    char *temp = encode_str(user);
    send(s_fd, temp, strlen(temp), 0);
    free(temp);
    print_response(s_fd, buf);
    temp = encode_str(pass);
    send(s_fd, temp, strlen(temp), 0);
    free(temp);
    print_response(s_fd, buf);

    // Send MAIL FROM command and print server response
    char MAIL_FROM[128] = "MAIL FROM:<"; 
    strcat(MAIL_FROM, user);
    strcat(MAIL_FROM, ">\r\n");
    send(s_fd, MAIL_FROM, strlen(MAIL_FROM), 0);
    print_response(s_fd, buf);

    // Send RCPT TO command and print server response
    char RCPT_TO[128] = "RCPT TO:<"; 
    strcat(RCPT_TO, receiver);
    strcat(RCPT_TO, ">\r\n");
    send(s_fd, RCPT_TO, strlen(RCPT_TO), 0);
    print_response(s_fd, buf);

    // Send DATA command and print server response
    char *DATA = "data\r\n";
    send(s_fd, DATA, strlen(DATA), 0);
    print_response(s_fd, buf);

    // Send message data
    
    // head
    if (subject == NULL)
        subject = "";
    char HEAD[MAX_SIZE + 1]; // 保证有足够空间
    const char *boundary = "A114514-1919810A";
    HEAD[0] = '\0';
    strcat(HEAD, "from:");
    strcat(HEAD, user);
    strcat(HEAD, "\r\n");
    strcat(HEAD, "to:");
    strcat(HEAD, receiver);
    strcat(HEAD, "\r\n");
    strcat(HEAD, "subject:");
    strcat(HEAD, subject);
    strcat(HEAD, "\r\n");
    strcat(HEAD, "MIME-Version: 1.0\r\n"); // 使用MIME发送
    strcat(HEAD, "Content-Type: multipart/mixed; boundary=\"");
    strcat(HEAD, boundary);
    strcat(HEAD, "\"\r\n\r\n");
    if (send(s_fd, HEAD, strlen(HEAD), 0) == -1){
        perror("send head");
        exit(EXIT_FAILURE);
    }

    // 正文
    HEAD[0] = '\0';
    strcat(HEAD, "--");
    strcat(HEAD, boundary);
    strcat(HEAD, "\r\n");
    strcat(HEAD, "Content-Type: text/plain; charset=\"us-ascii\"\r\n");
    strcat(HEAD, "Content-Transfer-Encoding: 7bit\r\n\r\n");
    if (send(s_fd, HEAD, strlen(HEAD), 0) == -1){
        perror("send head");
        exit(EXIT_FAILURE);
    }
    if (msg == NULL)
        goto att;
    struct stat st;
    FILE *input;
    FILE *output;
    if (stat(msg, &st) == 0 && S_ISREG(st.st_mode)){ // 是文件
        input = fopen(msg, "r");
        if (input == NULL){
            perror("open msg");
            exit(EXIT_FAILURE);
        }
        while ((r_size = fread(buf, 1, MAX_SIZE, input))){
            buf[r_size] = '\0'; 
            if (send(s_fd, buf, strlen(buf), 0) == -1){
                perror("send msg");
                exit(EXIT_FAILURE);
            }
        }
        fclose(input);
    }
    else {
        if (send(s_fd, msg, strlen(msg), 0) == -1){
            perror("send msg");
            exit(EXIT_FAILURE);
        }
    }
    
att:
    buf[0] = '\0';
    strcat(buf, "\r\n--");
    strcat(buf, boundary);
    strcat(buf, "\r\n");
    if (send(s_fd, buf, strlen(buf), 0) == -1){
        perror("send boundary");
        exit(EXIT_FAILURE);
    }
    // 附件
    if (att_path == NULL)
        goto end;
    HEAD[0] = '\0';
    strcat(HEAD, "Content-Type: application/octet-stream;\r\n");
    strcat(HEAD, "Content-Transfer-Encoding: base64\r\n");
    strcat(HEAD, "Content-Disposition: attachment; filename=\"");
    strcat(HEAD, att_path);
    strcat(HEAD, "\"\r\n\r\n");
    if (send(s_fd, HEAD, strlen(HEAD), 0) == -1){
        perror("send head");
        exit(EXIT_FAILURE);
    }
    input = fopen(att_path, "r");
    if (input == NULL){
        perror("open attachment");
        exit(EXIT_FAILURE);
    }
    output = fopen(".\\temp_attachment", "w+");
    if (output == NULL){
        perror("create temp_attachment");
        exit(EXIT_FAILURE);
    }
    encode_file(input, output);
    fseek(output , 0, SEEK_SET);
    while ((r_size = fread(buf, 1, MAX_SIZE, output))){
        buf[r_size] = '\0'; 
        if (send(s_fd, buf, strlen(buf), 0) == -1){
            perror("send attachment");
            exit(EXIT_FAILURE);
        }
    }
    remove(".\\temp_attachment");
    fclose(input);
    fclose(output);
    
end:
    buf[0] = '\0';
    strcat(buf, "\r\n--");
    strcat(buf, boundary);
    strcat(buf, "--\r\n");
    if (send(s_fd, buf, strlen(buf), 0) == -1){
        perror("send boundary");
        exit(EXIT_FAILURE);
    }

    // Message ends with a single period
    send(s_fd, end_msg, strlen(end_msg), 0);
    print_response(s_fd, buf);

    // Send QUIT command and print server response
    char *QUIT = "quit\r\n";
    send(s_fd, QUIT, strlen(QUIT), 0);
    print_response(s_fd, buf);

    close(s_fd);
}

int main(int argc, char* argv[])
{
    int opt;
    char* s_arg = NULL;
    char* m_arg = NULL;
    char* a_arg = NULL;
    char* recipient = NULL;
    const char* optstring = ":s:m:a:";
    while ((opt = getopt(argc, argv, optstring)) != -1)
    {
        switch (opt)
        {
        case 's':
            s_arg = optarg;
            break;
        case 'm':
            m_arg = optarg;
            break;
        case 'a':
            a_arg = optarg;
            break;
        case ':':
            fprintf(stderr, "Option %c needs an argument.\n", optopt);
            exit(EXIT_FAILURE);
        case '?':
            fprintf(stderr, "Unknown option: %c.\n", optopt);
            exit(EXIT_FAILURE);
        default:
            fprintf(stderr, "Unknown error.\n");
            exit(EXIT_FAILURE);
        }
    }

    if (optind == argc)
    {
        fprintf(stderr, "Recipient not specified.\n");
        exit(EXIT_FAILURE);
    }
    else if (optind < argc - 1)
    {
        fprintf(stderr, "Too many arguments.\n");
        exit(EXIT_FAILURE);
    }
    else
    {
        recipient = argv[optind];
        send_mail(recipient, s_arg, m_arg, a_arg);
        exit(0);
    }
}
